#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "errlib.h"
#include "sckts.h"
#include "transport_layer.h"

/* size of struct sockaddr_in data type */
#define SIZE		sizeof(struct sockaddr_in)
/* internal buffer size */
#define BUFFER_SIZE	512
/* first 1024 ports are reserved to known protocols */
#define RESERVED_PORTS 	1024
/* generate a pseudo-uniqueness relation between port and client */
#define MAX_CLIENT_PORT	(SERVER_PORT - RESERVED_PORTS)


/* static who_am_i variable */
static int isServer;
/* static file descriptor variable */
static int fd;
/* binded client port calculated upon its pid */
static int client_pid;
/* last client connection information */ 
static struct sockaddr_in client_data;
/* server connection information */
static struct sockaddr_in server_data;

static int sendDataServer(void * data, int size);
static int sendDataClient(void * data, int size);
static int recvDataServer(void * data);
static int recvDataClient(void * data);
static void init_client(void);
static void init_server(void);
static void close_client(void);
static void close_server(void);

/* send data through socket */
int 
sendData(void * data, int size)
{
	if(isServer)
		return sendDataServer(data, size);
	else
		return sendDataClient(data, size);
}

/* receive data through socket*/
int
recvData(void * data)
{
	if(isServer)
		return recvDataServer(data);
	else
		return recvDataClient(data);
}

/* init sockets configuration */
void 
init(int isThisTheServer)
{
 	isServer = isThisTheServer;
	
	if(isServer)
	{
		atexit(close_server);
		init_server();
	}
	else
	{
		atexit(close_client);
		init_client();
	}
}


/* 
**********************
** Auxiliar functions
**********************
*/

static int
sendDataServer(void * data, int size)
{
	unsigned char len;
	char buffer[BUFFER_SIZE];

	/* add size at the beginning of data */
	len = size;
	*buffer = len;
	memcpy(buffer + 1, data, size);

	/* send data to client */
	if (sendto(fd, buffer, size + 1, 0, 
		(struct sockaddr *)&client_data, SIZE) == -1)
	{
		warn("Server: Error while sending information");
		return -1;
	}
	
	return size;
}

static int
sendDataClient(void * data, int size)
{
	unsigned char len;
	char buffer[BUFFER_SIZE];

	/* add size at the beginning of data */
	len = size;
	*buffer = len;
	memcpy(buffer + 1, data, size);

	/* send data to server */
	if (sendto(fd, buffer, size + 1, 0, 
		(struct sockaddr *)&server_data, SIZE) == -1)
		fatal("Client: Error while sending information");

	return size;
}

static int
recvDataServer(void * data)
{
	char buffer[BUFFER_SIZE];
	int len = SIZE;

	/* receive complete data */
	if (recvfrom(fd, buffer, BUFFER_SIZE, 0, 
		(struct sockaddr *)&client_data, (socklen_t *)&len) == -1)
	{
		warn("Server: Error while receiving information");
		return -1;
	}

	/* read data size */
	len = *buffer;

	/* copy data from buffer to parameter */
	memcpy(data, buffer + 1, len);
	return len;		
}

static int
recvDataClient(void * data)
{
	char buffer[BUFFER_SIZE];
	int len = SIZE;

	/* receive complete data */
	if (recvfrom(fd, buffer, BUFFER_SIZE, 0, 
		(struct sockaddr *)&server_data, (socklen_t *)&len) == -1)
		fatal("Client: Error while receiving information");

	/* read data size */
	len = *buffer;

	/* copy data from buffer to parameter */
	memcpy(data, buffer + 1, len);
	return len;
}

static void
init_client(void)
{
	/* generate "unique" client port */
	client_pid = (getpid() % MAX_CLIENT_PORT) + RESERVED_PORTS;

 	struct sockaddr_in client = {AF_UNIX, client_pid, {INADDR_ANY}};
	/* server information */
	server_data.sin_family = AF_UNIX;
	server_data.sin_port = SERVER_PORT;		

	/* set up the transport end point, local communication using UDP */
	if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
		fatal("Client: Unknown error while creating socket");

	/* bind an address to the end point */
	if (bind(fd, (struct sockaddr *) &client, SIZE) == -1)
		fatal("Client: Unknown error while binding socket port");
}

static void
close_client(void)
{
	close(fd);
}

static void
init_server(void)
{
	/* server information */
	struct sockaddr_in server = {AF_UNIX, SERVER_PORT, {INADDR_ANY}};

	/* set up the transport end point, local communication using UDP */
	if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
		fatal("Server: Unknown error while creating socket");

	/* bind an address to the end point */
	if (bind(fd, (struct sockaddr *) &server, SIZE) == -1)
		fatal("Server: Unknown error while binding socket port");

}

static void
close_server(void)
{
	close(fd);
}

